package xyz.noark.network.rpc.shuttle;

import io.netty.channel.Channel;
import xyz.noark.core.network.NetworkProtocol;
import xyz.noark.core.util.DateUtils;
import xyz.noark.network.codec.AbstractPacketCodec;
import xyz.noark.network.rpc.RpcCallback;
import xyz.noark.network.rpc.RpcReqProtocol;
import xyz.noark.network.rpc.RpcStubManager;
import xyz.noark.network.rpc.stub.RpcAsyncStub;
import xyz.noark.network.rpc.stub.RpcSyncStub;

import java.io.Serializable;

import static xyz.noark.log.LogHelper.logger;

/**
 * Shuttle连接，封装了连接的通道和编解码器
 *
 * @author 小流氓[176543888@qq.com]
 * @since 4.0
 */
public class ShuttleConnection {
    private final Integer serverUid;
    private final Channel channel;
    private final RpcStubManager rpcStubManager;
    private final AbstractPacketCodec packetCodec;

    public ShuttleConnection(Integer serverUid, Channel channel, AbstractPacketCodec packetCodec) {
        this.serverUid = serverUid;
        this.channel = channel;
        this.packetCodec = packetCodec;
        this.rpcStubManager = RpcStubManager.getInstance();
    }

    public boolean isActive() {
        return channel.isActive();
    }

    public <T> void asyncCall(Serializable opcode, Object protocol, Class<T> ackClass, RpcCallback<T> callback) {
        // 1.生成请求唯一ID
        final Integer reqId = rpcStubManager.nextReqId();
        RpcAsyncStub<T> stub = new RpcAsyncStub<>(packetCodec, ackClass, callback);
        // 2.绑定存根
        rpcStubManager.bindStub(reqId, stub);
        try {
            // 3.发消息
            this.send(new RpcReqProtocol(reqId, opcode, protocol));
        }
        // 发送失败就解绑存根
        catch (Throwable e) {
            rpcStubManager.removeStub(reqId);
            callback.onFailure(e);
        }
    }

    public void asyncCall(Serializable opcode, Object protocol) {
        // 发送一个ReqId=0的请求
        this.send(new RpcReqProtocol(0, opcode, protocol));
    }

    public <T> T syncCall(Serializable opcode, Object protocol, Class<T> ackClass) {
        long startTime = System.nanoTime();
        // 1.生成请求唯一ID
        final Integer reqId = rpcStubManager.nextReqId();
        RpcSyncStub<T> stub = new RpcSyncStub<>(packetCodec, reqId, ackClass);
        try {
            // 2.绑定存根
            rpcStubManager.bindStub(reqId, stub);
            // 3.发消息
            this.send(new RpcReqProtocol(reqId, opcode, protocol));
            // 4.等待直到拿到结果
            return stub.waitTillResult();
        }
        // 5.解绑回调
        finally {
            rpcStubManager.removeStub(reqId);
            float exec = DateUtils.formatNanoTime(System.nanoTime() - startTime);
            logger.info("rpc sync call. reqId={}, exec={} ms", reqId, exec);
        }
    }

    public void send(NetworkProtocol protocol) {
        // 使用封包编码器去编码
        channel.writeAndFlush(packetCodec.encodePacket(protocol));
    }
}